package modify

import (
	"context"
	"fmt"
	"time"

	bytedtracer "code.byted.org/bytedtrace/interface-go"
	v1 "code.byted.org/bytertc/whiteboard_server/biz/api/pb/v1"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/biz_error"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/trace"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action/factory"
	"code.byted.org/bytertc/whiteboard_server/biz/service/command"
	"code.byted.org/bytertc/whiteboard_server/biz/service/command/base"
	"code.byted.org/bytertc/whiteboard_server/biz/service/replay"
	"code.byted.org/gopkg/logs/v2/log"
	"github.com/pkg/errors"
)

func NewEditingCommand(ctx context.Context, cmd *base.Command, packet *v1.Packet) (*editing, error) {
	actionsList, err := factory.NewActions(ctx, cmd.AppId, cmd.RoomId, packet, cmd.Repo)
	if err != nil {
		return nil, err
	}
	packet.Actions = nil
	serverTimestamp := uint64(time.Now().UnixMilli())
	packet.ServerTimestamp = &serverTimestamp
	c := replay.New(ctx, cmd.Repo, cmd.Config)
	return &editing{
		Command:   *cmd,
		Packet:    packet,
		acts:      actionsList,
		compactor: c,
	}, nil
}

type editing struct {
	base.Command
	*v1.Packet
	acts            []action.Action
	conflictObjects []string
	compactor       replay.Compactor
}

func (e *editing) Execute(ctx context.Context) []command.Result {
	//todo packetId 带来的收益是否大于复杂性
	//if err := e.Repo.UpdatePacketId(ctx, e, e.PacketId); err != nil {
	//	return e.generateEditingResp(ctx, err, nil)
	//}

	errorList := make([]error, len(e.acts))
	for index, act := range e.acts {
		tempErr := act.Execute(ctx)
		if tempErr != nil {
			errorList[index] = tempErr
		}
	}

	var firstError error
	for index, err := range errorList {
		if err == nil {
			e.Actions = append(e.Actions, e.acts[index].RawActions()...)
			continue
		} else if !e.acts[index].IsAtomic() {
			e.Actions = append(e.Actions, e.acts[index].RawActions()...)
		}

		if errors.Is(err, biz_error.Conflict) {
			e.conflictObjects = append(e.conflictObjects, e.acts[index].ConflictObjects()...)
			bytedtracer.AddEvents(ctx, bytedtracer.NewEvent(trace.EventTypeBiz, trace.ActionConflict.String()).
				SetTag("object_id", fmt.Sprintf("%v", e.acts[index].ConflictObjects())).
				SetTag("action_id", e.acts[index].GetActionId()).SetEmitLog(true))
			log.V1.CtxWarn(ctx, "%+v", err)
			//page operate only has err no conflictObjects
			if firstError == nil {
				firstError = err
			}
		} else if firstError == nil {
			firstError = err
		} else {
			log.V1.CtxError(ctx, "%+v", err)
		}
	}
	e.compactor.HandlePacket(ctx, e, e.Packet)
	userLockObjIds, err := e.Repo.GetLockedObjects(ctx, e)
	if err != nil {
		log.V1.CtxError(ctx, "fail to get GetLockedObjects data %+v", err)
	}

	return e.generateEditingResp(ctx, firstError, userLockObjIds)
}

func (e *editing) generateEditingResp(ctx context.Context, err error, userLockObjIds map[string]string) []command.Result {
	editingResp := &v1.EditingResponse{
		BoardId:  e.BoardId,
		PageId:   e.PageId,
		PacketId: e.PacketId, // todo 是否能使用用户的 packet id
		Status:   v1.ResponseStatus_Succeed,
	}
	uniCastRes := &base.Result{
		Msg:         editingResp,
		IsBroadcast: false,
		CommandType: v1.Command_kEditing,
		Error:       err,
	}
	broadcastRes := &base.Result{
		Msg: &v1.EditingMessage{
			Packet:         e.Packet,
			UserLockObjIds: userLockObjIds,
		},
		IsBroadcast: true,
		CommandType: v1.Command_kEditingMessage,
	}

	results := []command.Result{uniCastRes}
	if len(e.conflictObjects) != 0 || errors.Is(err, biz_error.Conflict) {
		editingResp.Status = v1.ResponseStatus_Conflict
		editingResp.ConflictObjIds = e.conflictObjects
	} else if err != nil {
		if errors.Is(err, biz_error.PacketReject) {
			editingResp.Status = v1.ResponseStatus_Reject
		} else {
			editingResp.Status = v1.ResponseStatus_Error
		}
	}
	if len(e.Actions) != 0 {
		results = append(results, broadcastRes)
	}

	return results
}
